package client_agent

import (
	"context"
	"fmt"
	"io"
	"lark/conf"
	"lark/consts"
	"lark/proto"
	"lark/util"
	"time"

	gproto "github.com/golang/protobuf/proto"

	"lark/helper"

	"github.com/pkg/errors"
)

//接收来自 client 的请求，形成 task 之后发送到 taskReqMsg
type client struct {
	id     string
	parser *proto.LarkProtoV2
	cp     *ClientProxy
	retCh  chan *clientOutMsg
	t      *time.Timer
}

func newClient(cp *ClientProxy, id string, parser *proto.LarkProtoV2) *client {
	return &client{
		id:     id,
		parser: parser,
		cp:     cp,
		t:      time.NewTimer(time.Second),

		retCh: make(chan *clientOutMsg, 1),
	}
}

//同步方式从 conn 读取请求和返回结果
func (c *client) run() {
	defer util.PrintPanicStack()
	defer c.close()

	for {
		//从 client 读取数据
		tag, payload, err := c.parser.Read()
		if err != nil {
			if err == io.EOF {
				logger.Debugf("[%s] read: %s", c.id, err)
			} else {
				logger.Errorf("[%s] read: %s", c.id, err)
			}
			return
		}

		if c.handle(tag, payload) != nil {
			return
		}
	}
}

func (c *client) handle(tag uint16, payload []byte) error {
	logger.Debugf("[%s] receive data tag:%d, value:%q", c.id, tag, payload)

	//根据 tag 处理数据
	switch tag {
	case consts.Lc2lcpTagTaskReq:
		msg, err := UnmarshalClientInMsg(payload)
		if err != nil {
			logger.Errorf("[%s] %s", err)
			c.parser.Write(consts.Lcp2lcTaskRespErrTry, []byte(err.Error()))
			return err
		}

		err = c.handleLc2lcpTaskReq(msg)
		if err != nil {
			return err
		}

	default:
		logger.Errorf("[%s] can't handle this tag. %d", c.id, tag)
		c.parser.Write(consts.Lcp2lcTaskRespErrTry, []byte(fmt.Sprintf("未知的tag:%d", tag)))
		return errors.New("wrong tag")
	}

	return nil
}

func (c *client) handleLc2lcpTaskReq(msg *clientInMsg) error {
	beginTime := time.Now()

	var processTimeout = msg.ProcessTimeout
	if processTimeout == 0 {
		processTimeout = conf.ClientAgentProcessTimeout()
	}

	ctx, cancel := context.WithTimeout(context.Background(), processTimeout)
	defer cancel()

	var tryNum = 1
try:
	service, err := c.cp.getService(msg.TargetService)
	if err != nil {
		c.parser.Write(consts.Lcp2lcTaskRespErrTry, []byte(fmt.Sprintf("找不到服务:%s", msg.TargetService)))
		logger.Errorf("[%s] get lark service error. %s", c.id, err)
		return err
	}

	logger.Debugf("[%s] get service success: service:%s", c.id, msg.TargetService)

	server, msgid, err := service.getServer(msg.TargetAddr)
	if err != nil {
		c.parser.Write(consts.Lcp2lcTaskRespErrTry, []byte(fmt.Sprintf("找不到服务下的实例:%s", msg.TargetService)))
		logger.Errorf("[%s] get server fail. error:%s", c.id, err.Error())
		return err
	}

	logger.Debugf("[%s] get server success: server_id:%s", c.id, server.id)

	b, err := gproto.Marshal(&proto.TaskReq{Id: msgid, Data: msg.Data})
	if err != nil {
		c.parser.Write(consts.Lcp2lcTaskRespErrTry, []byte(err.Error()))
		logger.Errorf("[%s] %s", c.id, err)
		return err
	}

	err = server.write(&sessionInMsg{msgid: msgid, retCh: c.retCh, data: b, createdAt: time.Now()}, ctx)
	if err != nil {
		if tryNum > 0 {
			tryNum--
			logger.Warnf("[%s] write data to server:%s fail. retry", server.id)
			goto try
		} else {
			logger.Errorf("[%s] write data to server:%s fail. drop. error:%s", c.id, server.id, err)
			c.parser.Write(consts.Lcp2lcTaskRespErrTry, []byte(fmt.Sprintf("请求发送失败, error:%s", err.Error())), helper.JsonMarshal(map[string]string{
				"server_ip": server.raddr,
			}))

			return err
		}
	}

	logger.Debugf("[%s] write data success: msgid:%d", c.id, msgid)

	//wait response from server
	var tag uint16
	var body []byte
	select {
	case outmsg := <-c.retCh:
		logger.Debugf("[%s] get response success. msgid:%d", c.id, msgid)
		tag = outmsg.tag
		body = outmsg.data

	case <-ctx.Done():
		logger.Errorf("[%s] get response timeout. msgid:%d, target ip:%s, receive time:%s, msg data:%q", c.id, msgid, server.raddr, processTimeout, msg.Data)
		tag = consts.Lcp2lcTaskRespErrDontTry
		body = []byte("请求已经发出，但是未收到对方返回")
	}

	err = c.parser.Write(tag, body, helper.JsonMarshal(map[string]string{
		"server_ip": server.raddr,
	}))

	if err != nil {
		logger.Errorf("[%s] write data to %s fail. error:%s, tag:%d, data:%q", c.id, c.parser.RemoteAddr(), err, tag, body)
	} else {
		logger.Debugf("[%s] write data to %s success, msgid:%d, tag:%d, data:%q, use time:%s", c.id, c.parser.RemoteAddr(), msgid, tag, body, time.Since(beginTime))
	}

	return err
}

func (c *client) close() {
	logger.Debugf("[%s] close", c.id)
	if c.parser != nil {
		c.parser.Close()
	}
}
